/*
 * Copyright (c) Ulrich Hilger, http://dev.uhilger.de, all rights reserved.
 *
 * Published under the terms and conditions of the Apache License 2.0,
 * see http://www.apache.org/licenses/LICENSE-2.0
 */

package de.uhilger.servicehub.view;

import de.uhilger.app.ui.GridBagHelper;
import de.uhilger.servicehub.ServiceHub;
import de.uhilger.servicehub.ctrl.action.AbstractServiceHubAction;
import de.uhilger.servicehub.ctrl.action.ActionResultListener;
import de.uhilger.servicehub.server.AppServer;
import de.uhilger.servicehub.server.AppServer.ServerPart;
import de.uhilger.servicehub.server.ServerStatusListener;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyVetoException;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;

/**
 * Panel to control the server of application ServiceHub
 *
 * @author Ulrich Hilger, <a href="http://dev.uhilger.de" target="_blank">http://dev.uhilger.de</a>
 * @author Published under the terms and conditions of
 * the <a href="http://www.apache.org/licenses/LICENSE-2.0" target="_blank">Apache License 2.0</a>
 */
public class ServerPanel extends JPanel implements ActionResultListener, ServerStatusListener, ActionListener, FocusListener {

  private static final String RN_SERVER_PANEL_WEB_APP_DIR_LABEL = "serverPanelWebAppDirLabel";
  private static final String RN_SERVER_PANEL_SERVER_STATUS_LABEL = "serverPanelServerStatusLabel";
  private static final String RN_SERVER_PANEL_SERVER_PORT_LABEL = "serverPanelServerPortLabel";
  private static final String RN_SERVER_PANEL_JRE_PATH_LABEL = "serverPanelJrePathLabel";
  private static final String RN_SERVER_PANEL_SRE_PATH_LABEL = "serverPanelSrePathLabel";
  private static final String RN_SERVER_PANEL_SERVER_INFO_LABEL = "serverPanelServerInfoLabel";
  private static final String RN_SERVER_PANEL_SERVER_STATUS_STARTED = "serverPanelServerStatusStarted";
  private static final String RN_SERVER_PANEL_SERVER_STATUS_STOPPED = "serverPanelServerStatusStopped";
  private static final String RN_SERVER_PANEL_DB_SERVER_INFO_LABEL = "serverPanelDbServerInfoLabel";
  private static final String RN_SERVER_PANEL_SERVER_DB_PORT_LABEL = "serverPanelServerDbPortLabel";
  private static final String RN_SERVER_PANEL_DB_USER = "serverPanelDbUserLabel";
  private static final String RN_SERVER_PANEL_DB_PASS = "serverPanelDbPassLabel";
  private static final String RN_SERVER_PANEL_CHANGE_DB_PASS_BTN = "serverPanelChangeDbPassBtnLabel";

  public static final String AC_SERVER_PANEL_WEB_DIR_SELECT = "serverPanelWebdirSelect";

  private JTextField webAppDir;
  private JTextField httpPort;
  private JTextField jdbcPort;
  private JButton webDirSelectBtn;
  private JButton sreSelectBtn;
  private JButton chgDbPassBtn;
  private JLabel serverStatus;
  private JLabel jrePath;
  private JTextField srePath;
  private JTextField dbUser;
  private JPasswordField dbPass;
  private JLabel serverInfo;
  private JLabel dbInfo;
  private ResourceBundle res;
  private int savedPort;
  //private String savedFieldContent;
  private boolean ignoreServerEvents = false;
  private ServiceHub owner;

  /**
   * Create a new object of class ServerPanel
   *
   * @param owner the application that owns this panel
   * @param dirSelAction  action to use for selection of directories
   */
  public ServerPanel(ServiceHub owner, AbstractServiceHubAction dirSelAction) {
    this.res = owner.getBundle();
    this.owner = owner;
    buildUi(dirSelAction);
  }

  /**
   * Create the user interface
   * @param dirSelAction  action to use to select directories
   */
  private void buildUi(AbstractServiceHubAction dirSelAction) {
    Preferences prefs = Preferences.userNodeForPackage(ServiceHub.class);
    dirSelAction.addActionResultListener(this);

    GridBagHelper gbh = new GridBagHelper();
    GridBagConstraints c = new GridBagConstraints();
    GridBagLayout g = new GridBagLayout();

    JPanel innerPanel = new JPanel(g);

    // line 0

    JLabel label = new JLabel(res.getString(RN_SERVER_PANEL_JRE_PATH_LABEL));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 0, GridBagConstraints.EAST);

    jrePath = new JLabel(System.getProperty("java.version") + " @ " + getJREPath());
    gbh.addGridBagComponent(innerPanel, jrePath, g, c, 1, 0, GridBagConstraints.WEST);

    // line 1

    label = new JLabel(res.getString(RN_SERVER_PANEL_SERVER_INFO_LABEL));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 1, GridBagConstraints.EAST);

    serverInfo = new JLabel("");
    gbh.addGridBagComponent(innerPanel, serverInfo, g, c, 1, 1, GridBagConstraints.WEST);

    // line 2

    label = new JLabel(res.getString(RN_SERVER_PANEL_DB_SERVER_INFO_LABEL));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 2, GridBagConstraints.EAST);

    dbInfo = new JLabel("");
    gbh.addGridBagComponent(innerPanel, dbInfo, g, c, 1, 2, GridBagConstraints.WEST);

    // line 3

    label = new JLabel(res.getString(RN_SERVER_PANEL_SERVER_STATUS_LABEL));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 3, GridBagConstraints.EAST);

    serverStatus = new JLabel(res.getString(RN_SERVER_PANEL_SERVER_STATUS_STOPPED));
    gbh.addGridBagComponent(innerPanel, serverStatus, g, c, 1, 3, GridBagConstraints.WEST);

    // line 4

    label = new JLabel(res.getString(RN_SERVER_PANEL_SRE_PATH_LABEL));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 4, GridBagConstraints.EAST);

    srePath = new JTextField("");
    srePath.setEditable(false);

    String path = prefs.get(ServiceHub.PREF_SRE_PATH, owner.getDefaultPath());
    srePath.setText(path);
    owner.setServerHome(srePath.getText());
    gbh.addGridBagComponent(innerPanel, srePath, g, c, 1, 4, GridBagConstraints.WEST,
            1, 1, GridBagConstraints.HORIZONTAL, 1, 1);

    sreSelectBtn = new JButton();
    sreSelectBtn.setAction(dirSelAction);
    sreSelectBtn.setText("..");
    gbh.addGridBagComponent(innerPanel, sreSelectBtn, g, c, 2, 4, GridBagConstraints.CENTER);
    sreSelectBtn.setVisible(true);

    // line 5

    label = new JLabel(res.getString(RN_SERVER_PANEL_WEB_APP_DIR_LABEL));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 5, GridBagConstraints.EAST);

    webAppDir = new JTextField();
    webAppDir.setEditable(false);
    path = prefs.get(ServiceHub.PREF_SERVER_PATH, owner.getDefaultPath());
    webAppDir.setText(path);
    gbh.addGridBagComponent(innerPanel, webAppDir, g, c, 1, 5, GridBagConstraints.WEST,
            1, 1, GridBagConstraints.HORIZONTAL, 1, 1);

    webDirSelectBtn = new JButton();
    webDirSelectBtn.setAction(dirSelAction);
    webDirSelectBtn.setActionCommand(AC_SERVER_PANEL_WEB_DIR_SELECT);
    webDirSelectBtn.setText("..");
    gbh.addGridBagComponent(innerPanel, webDirSelectBtn, g, c, 2, 5, GridBagConstraints.CENTER);

    // line 6

    label = new JLabel(res.getString(RN_SERVER_PANEL_SERVER_PORT_LABEL));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 6, GridBagConstraints.EAST);

    httpPort = new JTextField();
    httpPort.setMinimumSize(new Dimension(40, 20));
    httpPort.setPreferredSize(new Dimension(40, 20));
    int portNum = prefs.getInt(ServiceHub.PREF_SERVER_PORT, 8080);
    httpPort.setText(Integer.toString(portNum));
    gbh.addGridBagComponent(innerPanel, httpPort, g, c, 1, 6, GridBagConstraints.WEST,
            1, 1, GridBagConstraints.NONE, 1, 1);
    httpPort.addActionListener(this);
    httpPort.addFocusListener(this);

    // line 7

    label = new JLabel(res.getString(RN_SERVER_PANEL_SERVER_DB_PORT_LABEL));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 7, GridBagConstraints.EAST);

    jdbcPort = new JTextField();
    jdbcPort.setMinimumSize(new Dimension(40, 20));
    jdbcPort.setPreferredSize(new Dimension(40, 20));
    int dbPortNum = prefs.getInt(ServiceHub.PREF_SERVER_DB_PORT, 1527);
    jdbcPort.setText(Integer.toString(dbPortNum));
    gbh.addGridBagComponent(innerPanel, jdbcPort, g, c, 1, 7, GridBagConstraints.WEST,
            1, 1, GridBagConstraints.NONE, 1, 1);
    jdbcPort.addActionListener(this);
    jdbcPort.addFocusListener(this);

    // line 8

    label = new JLabel(res.getString(RN_SERVER_PANEL_DB_USER));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 8, GridBagConstraints.EAST);

    dbUser = new JTextField();
    String user = prefs.get(ServiceHub.PREF_DB_USER, "service_hub");
    dbUser.setText(user);
    owner.setDbUser(user);
    dbUser.addActionListener(this);
    dbUser.addFocusListener(this);
    boolean enterOwnLogin = prefs.getBoolean(ServiceHub.PREF_DEFAULT_DB_LOGIN, false);
    dbUser.setEnabled(enterOwnLogin);
    gbh.addGridBagComponent(innerPanel, dbUser, g, c, 1, 8, GridBagConstraints.WEST,
            1, 1, GridBagConstraints.HORIZONTAL, 1, 1);

    // line 9

    label = new JLabel(res.getString(RN_SERVER_PANEL_DB_PASS));
    gbh.addGridBagComponent(innerPanel, label, g, c, 0, 9, GridBagConstraints.EAST);

    dbPass = new JPasswordField();
    String cred = prefs.get(ServiceHub.PREF_DB_CRED, "storag3");
    dbPass.setMinimumSize(new Dimension(80, 20));
    dbPass.setPreferredSize(new Dimension(80, 20));
    dbPass.setEnabled(enterOwnLogin);
    dbPass.addActionListener(this);
    dbPass.addFocusListener(this);
    dbPass.setText(cred);
    owner.setDbPass(new String(dbPass.getPassword()));
    gbh.addGridBagComponent(innerPanel, dbPass, g, c, 1, 9, GridBagConstraints.WEST,
            1, 1, GridBagConstraints.NONE, 1, 1);

    chgDbPassBtn = new JButton();
    chgDbPassBtn.addActionListener(this);
    chgDbPassBtn.setText(res.getString(RN_SERVER_PANEL_CHANGE_DB_PASS_BTN));
    chgDbPassBtn.setEnabled(false);
    gbh.addGridBagComponent(innerPanel, chgDbPassBtn, g, c, 2, 9, GridBagConstraints.CENTER);
    chgDbPassBtn.setVisible(false);

    setLayout(new BorderLayout());
    add(innerPanel, BorderLayout.NORTH);
    setBorder(new EmptyBorder(12, 12, 12, 12));
  }

  /**
   * Get the path of the Java Runtime Environment this application runs on
   * @return  the JRE path
   */
  public String getJREPath() {
    URL url = System.class.getResource("System.class");
    ServiceHub.log("Java path: " + url, getClass(), Level.FINEST);
    String urlStr = url.toString();
    String prefix = ServiceHub.JAR_PREFIX + ServiceHub.FILE_PREFIX;
    String fileName = urlStr.substring(prefix.length(), urlStr.indexOf("/rt.jar"));
    try {
      fileName = URLDecoder.decode(fileName, "UTF-8");
    } catch (UnsupportedEncodingException ex) {
      Logger.getLogger(ServerPanel.class.getName()).log(Level.SEVERE, null, ex);
    }
    return new File(fileName).getAbsolutePath();
  }

  /**
   * Handle a result from an action
   * @param event  event that produced this result
   * @param result  the result to handle
   */
  public void resultPresent(ActionEvent event, Object result) {
    ServiceHub.log("action result " + event.getActionCommand() + ", result: " +
            result, getClass(), Level.FINEST);
    Object o = event.getSource();
    if (o != null) {
      if (o.equals(webDirSelectBtn)) {
        if (result instanceof File) {
          String path = ((File) result).getAbsolutePath();
          try {
            owner.getServer().setBase(path);
          } catch (PropertyVetoException ex) {
            Logger.getLogger(ServerPanel.class.getName()).log(Level.SEVERE, null, ex);
          }
        }
      } else if(o.equals(sreSelectBtn)) {
        if(result instanceof File) {
          String path = ((File) result).getAbsolutePath();
          srePath.setText(path);
          owner.setServerHome(path);
          Preferences prefs = Preferences.userNodeForPackage(ServiceHub.class);
          prefs.put(ServiceHub.PREF_SRE_PATH, path);
        }
      }
    }
  }

  /**
   * Handle a change of status of the server this serer panel is bound to
   * @param server  the server whose status changed
   * @param part  the part that changed status
   */
  public void serverStatusChanged(AppServer server, ServerPart part) {
    Preferences prefs = Preferences.userNodeForPackage(ServiceHub.class);
    if(!ignoreServerEvents) {
      switch(part) {
        case BASE:
          webAppDir.setText(server.getBase());
          break;
        case HOME:
          srePath.setText(server.getHome());
          break;
        case STATUS:
          if(server.isRunning()) {
            serverStatus.setText(res.getString(RN_SERVER_PANEL_SERVER_STATUS_STARTED));
            serverInfo.setText(server.getServletContainerInfo());
            dbInfo.setText(server.getDatabaseServerInfo());
            srePath.setEnabled(false);
            sreSelectBtn.setEnabled(false);
            webAppDir.setEnabled(false);
            webDirSelectBtn.setEnabled(false);
            httpPort.setEnabled(false);
            jdbcPort.setEnabled(false);
            setDbLoginEnabled(false);
            chgDbPassBtn.setEnabled(true);
          } else {
            serverStatus.setText(res.getString(RN_SERVER_PANEL_SERVER_STATUS_STOPPED));
            serverInfo.setText("");
            dbInfo.setText("");
            srePath.setEnabled(true);
            sreSelectBtn.setEnabled(true);
            webAppDir.setEnabled(true);
            webDirSelectBtn.setEnabled(true);
            httpPort.setEnabled(true);
            jdbcPort.setEnabled(true);
            boolean enterOwnLogin = prefs.getBoolean(ServiceHub.PREF_DEFAULT_DB_LOGIN, false);
            setDbLoginEnabled(enterOwnLogin);
            //dbUser.setEnabled(enterOwnLogin);
            //dbUser.setEnabled(true);
            //dbPass.setEnabled(enterOwnLogin);
            //dbPass.setEnabled(true);
            chgDbPassBtn.setEnabled(false);
          }
          break;
      }
    }
  }

  public void setDbLoginEnabled(boolean isEnabled) {
    dbUser.setEnabled(isEnabled);
    dbPass.setEnabled(isEnabled);
  }

  /**
   * Handle a port change
   */
  private void handlePortEvent(JTextField portField) {
    int currentPort = savedPort;
    try { currentPort = Integer.parseInt(portField.getText()); }
    catch(Throwable t) {}
    if(currentPort != savedPort) {
        ServiceHub.log("port changed to: " + portField.getText(), getClass(), Level.FINEST);
        ignoreServerEvents = true;
      try {
        AppServer server = owner.getServer();
        if(portField.equals(httpPort)) {
          server.setPort(currentPort);
        } else if(portField.equals(jdbcPort)) {
          server.setDbPort(currentPort);
        }
      } catch (PropertyVetoException ex) {
        Logger.getLogger(ServerPanel.class.getName()).log(Level.SEVERE, null, ex);
      } finally {
        ignoreServerEvents = false;
        savedPort = currentPort;
      }
    }
  }

  /**
   * Focus was lost in user database user name field, i.e. likely that user name was changed.
   */
  private void handleDbUserEvent() {
    owner.setDbUser(dbUser.getText());
    /*
    String user = dbUser.getText();
    String pw = new String(dbPass.getPassword());
    if(!savedFieldContent.equals(user)) {
      owner.setDbLogin(user, pw);
    }
    */
  }

  /**
   * Focus was lost in user database password field, i.e. likely that password was changed
   */
  private void handleDbPassEvent() {
    String pass = new String(dbPass.getPassword());
    Preferences prefs = Preferences.userNodeForPackage(ServiceHub.class);
    prefs.put(ServiceHub.PREF_DB_CRED, pass);
    owner.setDbPass(pass);
    /*
    String user = dbUser.getText();
    String pw = new String(dbPass.getPassword());
    if(!savedFieldContent.equals(pw)) {
      owner.setDbLogin(user, pw);
    }
    */
  }

  /**
   * User pressed 'change user database password' button durng server run
   */
  private void changeDbPassword() {
    // TODO handle change database password during server running, i.e. change password for db user and notify others
    owner.getServer().setDbPassword("test");
  }

  /**
   * Handle  a change of the port text field
   * @param e  the event that triggered this handler
   */
  public void actionPerformed(ActionEvent e) {
    Object src = e.getSource();
    if(src != null) {
      if(src instanceof JTextField) {
        JTextField field = (JTextField) src;
        ServiceHub.log("actionPerformed text: " + field.getText(), getClass(), Level.FINEST);
        if(field.equals(httpPort) || field.equals(jdbcPort)) {
          handlePortEvent(field);
        } else if(field.equals(dbUser)) {

        } else if(field.equals(dbPass)) {

        }
      } else if(src.equals(chgDbPassBtn)) {
        ServiceHub.log("actionPerformed chgDbPassBtn", getClass(), Level.FINEST);
        changeDbPassword();
      }
    }
  }

  /**
   * Handle the focus gained event from the port text field
   * @param e  the event that triggered this handler
   */
  public void focusGained(FocusEvent e) {
    Object src = e.getSource();
    if(src != null && src instanceof JTextField) {
      JTextField field = (JTextField) src;
      if(field.equals(httpPort) || field.equals(jdbcPort)) {
        ServiceHub.log("focusGained, saving port " + field.getText(), getClass(), Level.FINEST);
        try { savedPort = Integer.parseInt(field.getText()); }
        catch(Throwable t) {}
      } else if(field.equals(dbUser)) {
        //savedFieldContent = field.getText();
      } else if(field.equals(dbPass)) {
        //savedFieldContent = new String(((JPasswordField) field).getPassword());
      }
    }
  }

  /**
   * Handle the focus lost event from the port text field
   * @param e  the event that triggered this handler
   */
  public void focusLost(FocusEvent e) {
    Object src = e.getSource();
    if(src != null && src instanceof JTextField) {
      JTextField field = (JTextField) src;
      if(field.equals(httpPort) || field.equals(jdbcPort)) {
        ServiceHub.log("focusLost text: " + field.getText(), getClass(), Level.FINEST);
        handlePortEvent(field);
      } else if(field.equals(dbUser)) {
        Preferences prefs = Preferences.userNodeForPackage(ServiceHub.class);
        prefs.put(ServiceHub.PREF_DB_USER, dbUser.getText());
        handleDbUserEvent();
      } else if(field.equals(dbPass)) {
        handleDbPassEvent();
      }
    }
  }
  
}
